// Relationship.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

using Supremacy.Collections;
using Supremacy.Economy;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Types;

using Wintellect.PowerCollections;
using System.Collections;

namespace Supremacy.Diplomacy
{
    [Serializable]
    public class Relationship : INotifyPropertyChanged
    {
        #region Constants
        private const int AgreementHistoryThreshold = 200;
        private const int InboxHistoryThreshold = 20;
        private const int InvalidTurn = -1;
        private const int MinDesirableBorderBuffer = 2;
        private const int MinExpansionThreatThreshold = 10;
        #endregion

        #region Fields
        private readonly ArrayList<Agreement> _activeAgreements;

        private readonly History<Agreement> _agreementHistory;
        private readonly Meter _attitude;
        private readonly History<DiplomaticMessage> _inboxHistory;
        private readonly ArrayList<RelationshipMemory> _memories;

        private readonly int _otherCivId;
        private readonly int _ownerCivId;
        private readonly ArrayList<Agreement> _processedAgreements;

        [NonSerialized] private ReadOnlyCollection<Agreement> _activeAgreementsView;
        [NonSerialized] private ReadOnlyCollection<Agreement> _agreementHistoryView;

        private double _attitudeChange;
        private int _contactTurn = InvalidTurn;
        private DiplomaticMessage _inbox;
        private DiplomaticMessage _replyInbox;
        [NonSerialized] private Relationship _otherRelationship;
        [NonSerialized] private ReadOnlyCollection<DiplomaticMessage> _inboxHistoryView;
        private int _lastStatusChangeTurn = InvalidTurn;
        private int _lastTurnProcessed = InvalidTurn;
        private DiplomaticMessage _outbox;
        private DiplomaticMessage _replyOutbox;
        private RelationshipStatus _status;
        #endregion

        #region Constructors
        public Relationship(Civilization ownerCiv, Civilization otherCiv)
        {
            if (ownerCiv == null)
                throw new ArgumentNullException("ownerCiv");
            if (otherCiv == null)
                throw new ArgumentNullException("otherCiv");
            _ownerCivId = ownerCiv.CivID;
            _otherCivId = otherCiv.CivID;
            _activeAgreements = new ArrayList<Agreement>();
            _status = RelationshipStatus.Neutral;
            _inboxHistory = new History<DiplomaticMessage>(CompareMessagesByTurnSent);
            _agreementHistory = new History<Agreement>(CompareAgreementsByTurnAccepted);
            _processedAgreements = (OtherRelationship != null)
                ? OtherRelationship._processedAgreements
                : new ArrayList<Agreement>();
            _memories = new ArrayList<RelationshipMemory>();
            _attitude = new Meter(0, -100, 100);
        }
        #endregion

        #region Properties
        public IList<RelationshipMemory> Memories
        {
            get { return Algorithms.ReadOnly(_memories); }
        }

        public int OwnerCivID
        {
            get { return _ownerCivId; }
        }

        public int OtherCivID
        {
            get { return _otherCivId; }
        }

        public Civilization OwnerCiv
        {
            get { return GameContext.Current.Civilizations[_ownerCivId]; }
        }

        public Civilization OtherCiv
        {
            get { return GameContext.Current.Civilizations[_otherCivId]; }
        }

        public double AttitudeChange
        {
            get { return _attitudeChange; }
            set { _attitudeChange = value; }
        }

        public Relationship OtherRelationship
        {
            get
            {
                if (_otherRelationship == null)
                    _otherRelationship = GameContext.Current.Relationships[OtherCiv, OwnerCiv];
                return _otherRelationship;
            }
        }

        public RelationshipStatus Status
        {
            get { return _status; }
            internal set
            {
                if (_status == value)
                    return;

                if (!IsContactMade && (value != RelationshipStatus.Unknown))
                    MakeContact();

                _status = value;
                _lastStatusChangeTurn = GameContext.Current.TurnNumber;

                GameContext.Current.DiplomacyStatus[_ownerCivId, _otherCivId] = this.CurrentState;

                OnPropertyChanged("Status");
                OnPropertyChanged("CurrentState");
                OnPropertyChanged("TurnsSinceStatusChange");

                switch (_status)
                {
                    case RelationshipStatus.OwnerIsMember:
                        OtherRelationship._status = RelationshipStatus.OtherIsMember;
                        break;
                    case RelationshipStatus.OwnerIsProtectorate:
                        OtherRelationship._status = RelationshipStatus.OtherIsProtectorate;
                        break;
                    case RelationshipStatus.OwnerIsSubjugated:
                        OtherRelationship._status = RelationshipStatus.OwnerIsSubjugated;
                        break;
                    default:
                        OtherRelationship._status = _status;
                        break;
                }

                OtherRelationship._lastStatusChangeTurn = _lastStatusChangeTurn;

                GameContext.Current.DiplomacyStatus[_otherCivId, _ownerCivId] = this.CurrentState;

                OtherRelationship.OnPropertyChanged("Status");
                OtherRelationship.OnPropertyChanged("CurrentState");
                OtherRelationship.OnPropertyChanged("TurnsSinceStatusChange");
            }
        }

        public int TurnsSinceStatusChange
        {
            get { return (GameContext.Current.TurnNumber - _lastStatusChangeTurn); }
        }

        public bool AtWar
        {
            get { return (this.Status == RelationshipStatus.AtWar); }
        }

        public bool IsDiplomatAvailable
        {
            get
            {
                if (!IsContactMade)
                    return false;
                switch (this.Status)
                {
                    case RelationshipStatus.OtherIsSubjugated:
                        return false;
                    case RelationshipStatus.AtWar:
                        return (TurnsSinceStatusChange >= 2);
                    default:
                        return true;
                }
            }
        }

        public DiplomaticMessage Inbox
        {
            get { return _inbox; }
            set
            {
                if (_inbox != null)
                    _inboxHistory.Add(_inbox);
                _inbox = value;
                OnPropertyChanged("Inbox");
                OnPropertyChanged("HasIncomingMessage");
            }
        }

        public DiplomaticMessage ReplyInbox
        {
            get { return _replyInbox; }
            set
            {
                if (_replyInbox != null)
                    _inboxHistory.Add(_replyInbox);
                _replyInbox = value;
                OnPropertyChanged("ReplyInbox");
                OnPropertyChanged("HasIncomingReply");
            }
        }

        public DiplomaticMessage Outbox
        {
            get { return _outbox; }
            set
            {
                _outbox = value;
                OnPropertyChanged("Outbox");
                OnPropertyChanged("HasOutgoingMessage");
            }
        }

        public DiplomaticMessage ReplyOutbox
        {
            get { return _replyOutbox; }
            set
            {
                _replyOutbox = value;
                OnPropertyChanged("ReplyOutbox");
                OnPropertyChanged("HasOutgoingReply");
            }
        }

        public bool HasIncomingMessage
        {
            get { return (Inbox != null); }
        }

        public bool HasOutgoingMessage
        {
            get { return (Outbox != null); }
        }

        public bool HasIncomingReply
        {
            get { return (ReplyInbox != null); }
        }

        public bool HasOutgoingReply
        {
            get { return (ReplyOutbox != null); }
        }

        public ReadOnlyCollection<Agreement> ActiveAgreements
        {
            get
            {
                if (_activeAgreementsView == null)
                    _activeAgreementsView = new ReadOnlyCollection<Agreement>(_activeAgreements);
                return _activeAgreementsView;
            }
        }

        public int ContactTurn
        {
            get { return _contactTurn; }
        }

        public int ContactDuration
        {
            get
            {
                if (this.ContactTurn == InvalidTurn)
                    return InvalidTurn;
                return (GameContext.Current.TurnNumber - this.ContactTurn);
            }
        }

        public bool IsContactMade
        {
            get { return (this.ContactTurn != InvalidTurn); }
            set
            {
                if (value)
                {
                    MakeContact();
                }
                else if (IsContactMade)
                {
                    throw new InvalidOperationException(
                        "IsContactMade cannot be set to 'false' once it has a value of 'true'.");
                }
            }
        }

        public ReadOnlyCollection<DiplomaticMessage> InboxHistory
        {
            get
            {
                if (_inboxHistoryView == null)
                    _inboxHistoryView = new ReadOnlyCollection<DiplomaticMessage>(_inboxHistory);
                return _inboxHistoryView;
            }
        }

        public bool AreBordersOpen
        {
            get { return IsEffectActive(StipulationEffect.GrantShipPassage); }
        }

        public bool AreBordersClosed
        {
            get { return IsEffectActive(StipulationEffect.CloseBorders); }
        }

        public bool IsTradeAllowed
        {
            get { return IsEffectActive(StipulationEffect.AllowTrade); }
        }

        public bool AreSuppliesShared
        {
            get { return IsEffectActive(StipulationEffect.ShareSupplies); }
        }

        public bool IsIntelShared
        {
            get { return IsEffectActive(StipulationEffect.ShareIntel); }
        }

        public bool AreRepairsShared
        {
            get { return IsEffectActive(StipulationEffect.GrantRepairRights); }
        }

        public ReadOnlyCollection<Agreement> AgreementHistory
        {
            get
            {
                if (_agreementHistoryView == null)
                    _agreementHistoryView = new ReadOnlyCollection<Agreement>(_agreementHistory);
                return _agreementHistoryView;
            }
        }

        public bool CanMakeProposal
        {
            get
            {
                if (!IsDiplomatAvailable || IsMember || OtherRelationship.IsMember)
                    return false;
                return ((GetAvailableStipulations(null, null, false).Count > 0) || (GetAvailableStipulations(null, null, true).Count > 0));
            }
        }

        public bool Allied
        {
            get
            {
                return (OwnerCiv.IsEmpire && OtherCiv.IsEmpire)
                    ? (Status == RelationshipStatus.Allied)
                    : (this.IsMember || OtherRelationship.IsMember);
            }
        }

        public bool IsMember
        {
            get { return (Status == RelationshipStatus.OwnerIsMember); }
        }

        public bool IsFriendly
        {
            get
            {
                switch (this.Status)
                {
                    case RelationshipStatus.Affiliated:
                    case RelationshipStatus.Friendly:
                    case RelationshipStatus.OwnerIsMember:
                    case RelationshipStatus.OtherIsMember:
                    case RelationshipStatus.OwnerIsProtectorate:
                    case RelationshipStatus.OtherIsProtectorate:
                    case RelationshipStatus.Self:
                        return true;
                    default:
                        return false;
                }
            }
        }

        public DiplomacyStatus CurrentState
        {
            get
            {
                if (!IsContactMade)
                    return DiplomacyStatus.NoContact;
                switch (this.Status)
                {
                    case RelationshipStatus.Neutral:
                        return DiplomacyStatus.Neutral;
                    case RelationshipStatus.AtWar:
                        return DiplomacyStatus.War;
                    case RelationshipStatus.Affiliated:
                    case RelationshipStatus.Friendly:
                    case RelationshipStatus.Peace:
                        return DiplomacyStatus.Peace;
                    case RelationshipStatus.Allied:
                        return DiplomacyStatus.Alliance;
                    case RelationshipStatus.OwnerIsMember:
                    case RelationshipStatus.OtherIsMember:
                        return DiplomacyStatus.Membership;
                    case RelationshipStatus.OwnerIsSubjugated:
                    case RelationshipStatus.OtherIsSubjugated:
                        return DiplomacyStatus.Subjugation;
                    default:
                    case RelationshipStatus.Unknown:
                        return DiplomacyStatus.NoContact;
                }
            }
        }

        public Meter Attitude
        {
            get { return _attitude; }
        }
        #endregion

        #region Methods
        private static int CompareAgreementsByTurnAccepted(Agreement a, Agreement b)
        {
            if (ReferenceEquals(a, b))
                return 0;
            if (a == null)
                return -1;
            if (b == null)
                return 1;
            return a.TurnAccepted.CompareTo(b.TurnAccepted);
        }

        private static int CompareMessagesByTurnSent(DiplomaticMessage a, DiplomaticMessage b)
        {
            if (ReferenceEquals(a, b))
                return 0;
            if (a == null)
                return -1;
            if (b == null)
                return 1;
            return a.TurnSent.CompareTo(b.TurnSent);
        }

        public IList<object> GetValidMessageParameters(DiplomaticMessageType messageType)
        {
            int lastMessageTurn;
            List<object> results = new List<object>();

            if (!IsDiplomatAvailable)
                return results;

            switch (messageType)
            {
                case DiplomaticMessageType.CommendAssault:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.BombardedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.CommendInvasion:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.InvadedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.CommendRelationship:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.AcceptedTreaty, lastMessageTurn));
                    break;

                case DiplomaticMessageType.CommendSabotage:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.SabotagedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.CommendWar:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.DeclaredWarOnUs, lastMessageTurn, true));
                    break;

                case DiplomaticMessageType.DenounceAssault:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.BombardedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.DenounceInvasion:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.InvadedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.DenounceRelationship:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.AcceptedTreaty, lastMessageTurn));
                    break;

                case DiplomaticMessageType.DenounceSabotage:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.SabotagedUs, lastMessageTurn));
                    break;

                case DiplomaticMessageType.DenounceWar:
                    lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    results.AddRange(FindKnownCivsWithMemory(OtherCiv, MemoryType.DeclaredWarOnUs, lastMessageTurn));
                    break;
            }

            return results;
        }

        public bool CanSendMessage(DiplomaticMessageType messageType)
        {
            if (!IsDiplomatAvailable)
                return false;

            switch (messageType)
            {
                case DiplomaticMessageType.WarDeclaration:
                    return !AtWar && !Allied;

                case DiplomaticMessageType.Proposal:
                case DiplomaticMessageType.ProposalAcceptance:
                    return CanMakeProposal;
                case DiplomaticMessageType.CounterProposal:
                    return (((HasIncomingMessage && Inbox.IsProposal)
                             || (HasIncomingReply && ReplyInbox.IsProposal))
                            && CanMakeProposal);

                case DiplomaticMessageType.ProposalRejection:
                    return true;

                case DiplomaticMessageType.AgreementTermination:
                    return (ActiveAgreements.Count > 0);

                case DiplomaticMessageType.CommendAssault:
                case DiplomaticMessageType.CommendInvasion:
                case DiplomaticMessageType.CommendRelationship:
                case DiplomaticMessageType.CommendSabotage:
                case DiplomaticMessageType.CommendWar:
                case DiplomaticMessageType.DenounceAssault:
                case DiplomaticMessageType.DenounceInvasion:
                case DiplomaticMessageType.DenounceRelationship:
                case DiplomaticMessageType.DenounceSabotage:
                case DiplomaticMessageType.DenounceWar:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    return (GetValidMessageParameters(messageType).Count > 0);

                case DiplomaticMessageType.ThreatAggression:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    return HasMemory(MemoryType.AttackedUs, GetLastStatementIssueTurn(messageType));

                case DiplomaticMessageType.ThreatAssault:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    return HasMemory(MemoryType.BombardedUs, GetLastStatementIssueTurn(messageType));

                case DiplomaticMessageType.ThreatExpansion:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    int lastMessageTurn = GetLastStatementIssueTurn(messageType);
                    if ((lastMessageTurn == InvalidTurn) || (lastMessageTurn > MinExpansionThreatThreshold))
                    {
                        int minDistance = CivHelper.GetClosestDistanceBetweenBorderClaims(OwnerCiv, OtherCiv);
                        return ((minDistance >= 0) && (minDistance < MinDesirableBorderBuffer));
                    }
                    return false;

                case DiplomaticMessageType.ThreatSpying:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    return HasMemory(MemoryType.SpiedOnUs, GetLastStatementIssueTurn(messageType));

                case DiplomaticMessageType.ThreatSabotage:
                    if (IsMember || OtherRelationship.IsMember)
                        return false;
                    return HasMemory(MemoryType.SabotagedUs, GetLastStatementIssueTurn(messageType));
            }
            return false;
        }

        public void DeclareWar()
        {
            if (AtWar)
            {
                return;
            }
            this.Status = RelationshipStatus.AtWar;
            // Have the the victim retract any proposals that they sent prior to the war declaration.
            if (OtherRelationship.Outbox != null)
            {
                if (!OtherRelationship.Outbox.IsStatement)
                {
                    // TODO: Retract message.
                    OtherRelationship.Outbox = null;
                }
            }
            //// Add the SitRep entries for all civs who have made contact with the warring parties.
            //foreach (Civilization civ in GameContext.Current.Civilizations)
            //{
            //    CivilizationManager civManager = null;
            //    if (civ == OwnerCiv)
            //    {
            //        civManager = GameContext.Current.CivilizationManagers[OwnerCiv];
            //    }
            //    else if (civ == OtherCiv)
            //    {
            //        civManager = GameContext.Current.CivilizationManagers[OtherCiv];
            //    }
            //    else if (DiplomacyHelper.IsContactMade(OwnerCiv, civ) || DiplomacyHelper.IsContactMade(OtherCiv, civ))
            //    {
            //        civManager = GameContext.Current.CivilizationManagers[civ];
            //    }
            //    if (civManager != null)
            //    {
            //        civManager.SitRepEntries.Add(new WarDeclaredSitRepEntry(civ, OwnerCiv, OtherCiv));
            //    }
            //}
        }

        private IList<Stipulation> GetActiveStipulations(bool getDemands)
        {
            List<Stipulation> stipulations = new List<Stipulation>();
            foreach (Agreement agreement in ActiveAgreements)
            {
                foreach (Stipulation stipulation in (getDemands ? agreement.Proposal.DemandStipulations : agreement.Proposal.OfferStipulations))
                {
                    if (!stipulations.Contains(stipulation))
                        stipulations.Add(stipulation);
                }
            }
            return stipulations;
        }

        public IList<Stipulation> GetAvailableStipulations(IList<Stipulation> existingOffers, IList<Stipulation> existingDemands, bool getDemands)
        {
            List<Stipulation> availableStipulations = new List<Stipulation>();
            List<Stipulation> unavailableStipulations = new List<Stipulation>();

            if (!IsDiplomatAvailable || (Status == RelationshipStatus.OwnerIsMember))
                return availableStipulations;

            if (existingOffers == null)
                existingOffers = new List<Stipulation>();

            if (Status == RelationshipStatus.OtherIsMember)
                unavailableStipulations.Add(new GiftStipulation());

            foreach (Stipulation stipulation in Stipulation.AllStipulations)
            {
                bool canAdd = true;
                if (unavailableStipulations.Contains(stipulation))
                    continue;
                if ((stipulation is AllianceStipulation) && !OtherCiv.IsEmpire)
                    continue;
                if ((stipulation is MembershipStipulation) && OtherCiv.IsEmpire)
                    continue;
                if ((stipulation is TreatyStipulation) && IsStipulationActive(stipulation.GetType()))
                {
                    continue;
                }
                foreach (Stipulation existingStipulation in (getDemands ? existingDemands : existingOffers).Union(GetActiveStipulations(getDemands)))
                {
                    if (existingStipulation.IsSupersetOf(stipulation)
                        && (stipulation.IsDurationFixed || (stipulation is TreatyStipulation)))
                    {
                        canAdd = false;
                    }
                }
                if ((getDemands ? existingOffers : existingDemands) != null)
                {
                    foreach (Stipulation existingStipulation in (getDemands ? existingOffers : existingDemands).Union(GetActiveStipulations(!getDemands)))
                    {
                        if (existingStipulation.IsMutual && stipulation.IsMutual)
                        {
                            canAdd = false;
                        }
                    }
                }
                if (stipulation.HasParameter && (stipulation.Parameter == null))
                {
                    var offerParams = stipulation.GetParameterList(new DiplomacyContext(OwnerCiv, OtherCiv, false));
                    if (offerParams.Count == 0)
                    {
                        var demandParams = stipulation.GetParameterList(new DiplomacyContext(OwnerCiv, OtherCiv, true));
                        if (demandParams.Count == 0)
                        {
                            canAdd = false;
                        }
                    }
                }
                if (canAdd)
                {
                    availableStipulations.Add(stipulation);
                }
            }

            return availableStipulations;
        }

        private void HandleIncomingMessage(DiplomaticMessage message)
        {
            if (message == null)
                return;

            if (message.IsDemand)
            {
                AddMemory(CreateMemory(MemoryType.MadeDemand));
            }
            else if (message.IsWarDeclaration)
            {
                AddMemory(CreateMemory(MemoryType.DeclaredWarOnUs));
                foreach (Civilization civ in GameContext.Current.Civilizations)
                {
                    if ((civ != OwnerCiv) && (civ != OtherCiv))
                    {
                        Relationship relationship = GameContext.Current.Relationships[civ, OtherCiv];
                        if ((relationship != null) && relationship.IsFriendly)
                        {
                            relationship.AddMemory(CreateMemory(MemoryType.DeclaredWarOnFriend));
                        }
                    }
                }
            }

            switch (message.MessageType)
            {
                case DiplomaticMessageType.Proposal:
                    if (!OwnerCiv.IsHuman)
                    {
                        ReplyOutbox = DiplomaticMessage.CreateProposalAcceptance(message.Proposal, GetDemeanor(true));
                        AcceptProposal(message.Proposal);
                    }
                    break;

                case DiplomaticMessageType.ProposalAcceptance:
                    OtherRelationship.AcceptProposal(message.Proposal);
                    break;

                case DiplomaticMessageType.ProposalRejection:
                    OtherRelationship.RejectProposal(message.Proposal);
                    break;
            }
        }

        public void HandleIncomingMessage()
        {
            HandleIncomingMessage(ReplyInbox);
            HandleIncomingMessage(Inbox);
        }

        internal void AcceptProposal(Proposal proposal)
        {
            if (!IsDiplomatAvailable || (proposal.Recipient != OwnerCiv) || (proposal.Sender != OtherCiv))
                return;

            AddAgreement(new Agreement(proposal));

            if (proposal.IsGift)
            {
                CivilizationManager civManager = GameContext.Current.CivilizationManagers[_ownerCivId];
                RelationshipMemory memory = CreateMemory(MemoryType.GaveGift);
                int giftValue = memory.Value;
                int stockpile = civManager.Credits.CurrentValue / 2;
                int offered = 0;
                foreach (Stipulation stipulation in proposal.OfferStipulations)
                {
                    GiftStipulation gift = stipulation as GiftStipulation;
                    if (gift != null)
                    {
                        offered += gift.GiftDetails.Credits;
                        foreach (ResourceType resource in Enum.GetValues(typeof(ResourceType)))
                        {
                            offered += (gift.GiftDetails.Resources[resource]
                                * DiplomacyHelper.GetResourceCreditValue(resource));
                        }
                    }
                }
                foreach (ResourceType resource in Enum.GetValues(typeof(ResourceType)))
                {
                    stockpile += (civManager.Resources[resource].CurrentValue / 2);
                }
                if (offered <= 0)
                {
                    giftValue = 0;
                }
                else if (offered < stockpile)
                {
                    giftValue = (giftValue * offered) / stockpile;
                }
                memory.Value = giftValue;
                AddMemory(memory);
            }
            else if (proposal.IsDemand)
            {
                OtherRelationship.AddMemory(CreateMemory(MemoryType.AcceptedDemand));
            }

            foreach (Stipulation demand in proposal.DemandStipulations)
            {
                if (demand is TreatyStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.AcceptedTreaty));
                }
                else if (demand is DeclareWarStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.AcceptedWarPact));
                }
                else if (demand is TerminateAgreementsStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.AcceptedStopTrading));
                }
            }

            foreach (Stipulation offer in proposal.OfferStipulations)
            {
                if (offer is TreatyStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.AcceptedTreaty));
                }
            }

            //GameContext.Current.CivilizationManagers[OwnerCiv].SitRepEntries.Add(
            //    new ProposalAcceptedSitRepEntry(OwnerCiv, proposal));
            //GameContext.Current.CivilizationManagers[OtherCiv].SitRepEntries.Add(
            //    new ProposalAcceptedSitRepEntry(OtherCiv, proposal));
        }

        internal void AddMemory(RelationshipMemory memory)
        {
            _memories.Add(memory);
        }

        internal RelationshipMemory CreateMemory(MemoryType memoryType)
        {
            return CreateMemory(memoryType, -1);
        }

        internal RelationshipMemory CreateMemory(MemoryType memoryType, int parameter)
        {
            return new RelationshipMemory(
                memoryType,
                DiplomacyHelper.GetInitialMemoryWeight(OwnerCiv, memoryType),
                parameter);
        }

        internal bool ExistsKnownCivWithMemory(Civilization offender, MemoryType memoryType)
        {
            return ExistsKnownCivWithMemory(offender, memoryType, 0);
        }

        internal bool ExistsKnownCivWithMemory(Civilization offender, MemoryType memoryType, int afterTurn)
        {
            return (FindKnownCivsWithMemory(offender, memoryType, afterTurn, false).Count > 0);
        }

        internal bool ExistsKnownCivWithMemory(Civilization offender, MemoryType memoryType, int afterTurn, bool checkOffender)
        {
            return (FindKnownCivsWithMemory(offender, memoryType, 0, checkOffender).Count > 0);
        }

        internal IList<object> FindKnownCivsWithMemory(Civilization offender, MemoryType memoryType)
        {
            return FindKnownCivsWithMemory(offender, memoryType, 0);
        }

        internal IList<object> FindKnownCivsWithMemory(Civilization offender, MemoryType memoryType, int afterTurn)
        {
            return FindKnownCivsWithMemory(offender, memoryType, 0, false);
        }

        internal IList<object> FindKnownCivsWithMemory(Civilization offender, MemoryType memoryType, int afterTurn, bool checkOffender)
        {
            if (offender == null)
                throw new ArgumentNullException("offender");
            List<object> results = new List<object>();
            foreach (Civilization civ in GameContext.Current.Civilizations)
            {
                if (results.Contains(civ))
                {
                    continue;
                }
                if ((civ != OwnerCiv) && (civ != offender))
                {
                    Relationship relationship = GameContext.Current.Relationships[civ, offender];
                    if ((relationship != null) && relationship.IsContactMade)
                    {
                        foreach (RelationshipMemory memory in relationship.Memories)
                        {
                            if ((memory.MemoryType == memoryType)
                                && ((afterTurn == InvalidTurn) || (memory.TurnCreated < afterTurn)))
                            {
                                if (!results.Contains(civ))
                                    results.Add(civ);
                                break;
                            }
                        }
                        if (checkOffender)
                        {
                            relationship = GameContext.Current.Relationships[offender, civ];
                            if (relationship != null)
                            {
                                foreach (RelationshipMemory memory in relationship.Memories)
                                {
                                    if ((memory.MemoryType == memoryType)
                                        && ((afterTurn == InvalidTurn) || (memory.TurnCreated < afterTurn)))
                                    {
                                        if (!results.Contains(civ))
                                            results.Add(civ);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return results;
        }

        internal int GetLastStatementIssueTurn(DiplomaticMessageType messageType)
        {
            switch (messageType)
            {
                case DiplomaticMessageType.CommendAssault:
                case DiplomaticMessageType.CommendInvasion:
                case DiplomaticMessageType.CommendRelationship:
                case DiplomaticMessageType.CommendSabotage:
                case DiplomaticMessageType.CommendWar:
                case DiplomaticMessageType.DenounceAssault:
                case DiplomaticMessageType.DenounceInvasion:
                case DiplomaticMessageType.DenounceRelationship:
                case DiplomaticMessageType.DenounceSabotage:
                case DiplomaticMessageType.DenounceWar:
                case DiplomaticMessageType.ThreatAssault:
                case DiplomaticMessageType.ThreatAggression:
                case DiplomaticMessageType.ThreatExpansion:
                case DiplomaticMessageType.ThreatSpying:
                    foreach (DiplomaticMessage message in OtherRelationship.InboxHistory)
                    {
                        if (message.MessageType == messageType)
                        {
                            return message.TurnSent;
                        }
                    }
                    break;
            }
            return InvalidTurn;
        }

        internal bool HasMemory(MemoryType memoryType)
        {
            return HasMemory(memoryType, InvalidTurn);
        }

        internal bool HasMemory(MemoryType memoryType, int afterTurn)
        {
            foreach (RelationshipMemory memory in Memories)
            {
                if ((memory.MemoryType == memoryType)
                    && ((afterTurn == InvalidTurn) || (memory.TurnCreated < afterTurn)))
                {
                    return true;
                }
            }
            return false;
        }

        internal void MakeContact()
        {
            if (!IsContactMade)
            {
                _contactTurn = GameContext.Current.TurnNumber;
                _status = RelationshipStatus.Neutral;
                _lastStatusChangeTurn = _contactTurn;
                
                this.UpdateStatus();
                
                OtherRelationship.MakeContact();
            }
        }

        internal void RejectProposal(Proposal proposal)
        {
            if (proposal.IsDemand)
            {
                OtherRelationship.AddMemory(CreateMemory(MemoryType.RejectedDemand));
            }

            foreach (Stipulation demand in proposal.DemandStipulations)
            {
                if (demand is TreatyStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.RejectedTreaty));
                }
                else if (demand is DeclareWarStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.RejectedWarPact));
                }
                else if (demand is TerminateAgreementsStipulation)
                {
                    OtherRelationship.AddMemory(CreateMemory(MemoryType.RejectedStopTrading));
                }
            }

            //GameContext.Current.CivilizationManagers[OwnerCiv].SitRepEntries.Add(
            //    new ProposalRejectedSitRepEntry(OwnerCiv, proposal));
            //GameContext.Current.CivilizationManagers[OtherCiv].SitRepEntries.Add(
            //    new ProposalRejectedSitRepEntry(OtherCiv, proposal));
        }

        internal int RemoveMemories(Predicate<RelationshipMemory> _condition)
        {
            int removed = 0;
            IEnumerable<RelationshipMemory> removedMemories = Algorithms.FindWhere(_memories, _condition);
            foreach (RelationshipMemory removedMemory in removedMemories)
            {
                if (_memories.Remove(removedMemory))
                    ++removed;
            }
            return removed;
        }

        internal int RemoveMemories(params MemoryType[] memoryTypes)
        {
            int removed = 0;
            for (int i = 0; i < _memories.Count; i++)
            {
                if (((ICollection<MemoryType>)memoryTypes).Contains(_memories[i].MemoryType))
                {
                    _memories.RemoveAt(i--);
                    ++removed;
                }
            }
            return removed;
        }

        internal bool RemoveMemory(RelationshipMemory memory)
        {
            return _memories.Remove(memory);
        }

        private void AddAgreement(Agreement agreement)
        {
            if (agreement == null)
                throw new ArgumentNullException("agreement");
            if (!_activeAgreements.Contains(agreement))
            {
                List<Agreement> removedAgreements = new List<Agreement>();
                foreach (Agreement oldAgreement in _activeAgreements)
                {
                    bool superceded = true;
                    foreach (Stipulation stipulation in agreement.Proposal.OfferStipulations)
                    {
                        foreach (Stipulation oldStipulation in oldAgreement.Proposal.OfferStipulations)
                        {
                            if (!stipulation.IsSupersetOf(oldStipulation))
                            {
                                superceded = false;
                                break;
                            }
                        }
                        if (!superceded)
                        {
                            break;
                        }
                    }
                    foreach (Stipulation stipulation in agreement.Proposal.DemandStipulations)
                    {
                        if (!superceded)
                        {
                            break;
                        }
                        foreach (Stipulation oldStipulation in oldAgreement.Proposal.DemandStipulations)
                        {
                            if (!stipulation.IsSupersetOf(oldStipulation))
                            {
                                superceded = false;
                                break;
                            }
                        }
                    }
                    if (superceded)
                    {
                        removedAgreements.Add(oldAgreement);
                    }
                }
                foreach (Agreement removedAgreement in removedAgreements)
                {
                    RemoveAgreement(removedAgreement);
                }
                _activeAgreements.Add(agreement);
                OtherRelationship._activeAgreements.Add(agreement);
            }
        }

        private void FindInactiveAgreements(ICollection<Agreement> inactiveAgreements)
        {
            if (AtWar && (TurnsSinceStatusChange == 0))
            {
                foreach (Agreement agreement in _activeAgreements)
                {
                    TerminationContext context = new TerminationContext(
                        agreement,
                        null,
                        TerminationReason.Failed);
                    foreach (Stipulation stipulation in agreement.Proposal.OfferStipulations)
                    {
                        if (!stipulation.IsTerminated)
                            stipulation.Terminate(context);
                    }
                    foreach (Stipulation stipulation in agreement.Proposal.DemandStipulations)
                    {
                        if (!stipulation.IsTerminated)
                            stipulation.Terminate(context);
                    }
                    inactiveAgreements.Add(agreement);
                }
                return;
            }

            foreach (Agreement agreement in _activeAgreements)
            {
                IList<Stipulation> stipulations = agreement.Proposal.OfferStipulations;
                DiplomacyContext diplomacyContext = new DiplomacyContext(agreement.Sender, agreement.Recipient, (agreement.Sender == OwnerCiv));
                for (int i = 0; i < stipulations.Count; i++)
                {
                    if (!stipulations[i].IsTerminated)
                    {
                        TerminationContext terminationContext = null;
                        if (stipulations[i].HasCustomDuration
                            && ((agreement.TurnAccepted + stipulations[i].Duration) < GameContext.Current.TurnNumber))
                        {
                            terminationContext = new TerminationContext(agreement, null, TerminationReason.Expired);
                        }
                        else if (!stipulations[i].CanExecute(diplomacyContext))
                        {
                            terminationContext = new TerminationContext(agreement, null, TerminationReason.Failed);
                        }
                        if (terminationContext != null)
                        {
                            stipulations[i].Terminate(terminationContext);
                        }
                    }
                }
                stipulations = agreement.Proposal.DemandStipulations;
                diplomacyContext = new DiplomacyContext(
                    agreement.Sender,
                    agreement.Recipient,
                    (agreement.Recipient == OwnerCiv));
                for (int i = 0; i < stipulations.Count; i++)
                {
                    if (!stipulations[i].IsTerminated)
                    {
                        TerminationContext terminationContext = null;
                        if (stipulations[i].HasCustomDuration
                            && ((agreement.TurnAccepted + stipulations[i].Duration) < GameContext.Current.TurnNumber))
                        {
                            terminationContext = new TerminationContext(agreement, null, TerminationReason.Expired);
                        }
                        else if (!stipulations[i].CanExecute(diplomacyContext))
                        {
                            terminationContext = new TerminationContext(agreement, null, TerminationReason.Failed);
                        }
                        if (terminationContext != null)
                        {
                            stipulations[i].Terminate(terminationContext);
                        }
                    }
                }
                if (!agreement.CheckActive())
                {
                    inactiveAgreements.Add(agreement);
                }
            }
        }

        public Demeanor Demeanor
        {
            get { return Demeanor.Calm; }
        }

        private Demeanor GetDemeanor(bool favorLastExchange)
        {
            //// TODO: Do a real implementation of Demeanor...
            //if (Inbox != null)
            //{
            //    switch (Inbox.MessageType)
            //    {
            //        case DiplomaticMessageType.Proposal:
            //            return favorLastExchange ? Demeanor.Receptive : Demeanor.Calm;
            //        case DiplomaticMessageType.CounterProposal:
            //            return favorLastExchange ? Demeanor.Impatient : Demeanor.Annoyed;
            //        case DiplomaticMessageType.AgreementTermination:
            //            return favorLastExchange ? Demeanor.Annoyed : Demeanor.Engraged;
            //    }
            //}
            return Demeanor.Calm;
        }

        private ICollection<Agreement> ProcessAgreements()
        {
            List<Agreement> inactiveAgreements = new List<Agreement>();

            if ((this._lastTurnProcessed != GameContext.Current.TurnNumber)
                && (OtherRelationship._lastTurnProcessed != GameContext.Current.TurnNumber))
            {
                this._processedAgreements.Clear();
            }

            foreach (Agreement agreement in _activeAgreements)
            {
                if (agreement.IsActive && !OtherRelationship._processedAgreements.Contains(agreement))
                {
                    DiplomacyContext context = new DiplomacyContext(
                        agreement.Sender,
                        agreement.Recipient,
                        (OwnerCiv == agreement.Recipient));
                    foreach (Stipulation stipulaton in agreement.Proposal.OfferStipulations)
                    {
                        if (stipulaton.IsPending)
                        {
                            stipulaton.Apply(context);
                        }
                        if (stipulaton.CanExecute(context))
                        {
                            stipulaton.Execute(context);
                        }
                        else
                        {
                            stipulaton.Terminate(new TerminationContext(agreement, null, TerminationReason.Failed));
                        }
                    }
                    context = new DiplomacyContext(
                        agreement.Sender,
                        agreement.Recipient,
                        (OtherCiv == agreement.Recipient));
                    foreach (Stipulation stipulaton in agreement.Proposal.DemandStipulations)
                    {
                        if (stipulaton.IsPending)
                        {
                            stipulaton.Apply(context);
                        }
                        if (stipulaton.CanExecute(context))
                        {
                            stipulaton.Execute(context);
                        }
                        else
                        {
                            stipulaton.Terminate(new TerminationContext(agreement, null, TerminationReason.Failed));
                        }
                    }
                }
                if (!agreement.IsActive)
                {
                    inactiveAgreements.Add(agreement);
                }
                _processedAgreements.Add(agreement);
            }

            _lastTurnProcessed = GameContext.Current.TurnNumber;
            if (OtherRelationship._lastTurnProcessed == this._lastTurnProcessed)
            {
                this._processedAgreements.Clear();
            }

            return inactiveAgreements;
        }

        private void RemoveAgreement(Agreement agreement)
        {
            if (agreement == null)
                throw new ArgumentNullException("agreement");
            if (_activeAgreements.Contains(agreement))
            {
                _agreementHistory.Add(agreement);
                OtherRelationship._agreementHistory.Add(agreement);
                _activeAgreements.Remove(agreement);
                OtherRelationship._activeAgreements.Remove(agreement);
            }
        }

        protected internal bool IsEffectActive(StipulationEffect effect)
        {
            bool isActiveForUs, isActiveForThem;
            return IsEffectActive(effect, out isActiveForUs, out isActiveForThem);
        }

        protected internal bool IsEffectActive(StipulationEffect effect, out bool isActiveForUs, out bool isActiveForThem)
        {
            bool result = false;
            isActiveForUs = false;
            isActiveForThem = false;
            foreach (Agreement agreement in _activeAgreements)
            {
                if (agreement.IsActive)
                {
                    IList<Stipulation> ourStipulations = (agreement.Sender == this.OwnerCiv)
                        ? agreement.Proposal.OfferStipulations
                        : agreement.Proposal.DemandStipulations;
                    IList<Stipulation> theirStipulations = (agreement.Sender == this.OtherCiv)
                        ? agreement.Proposal.OfferStipulations
                        : agreement.Proposal.DemandStipulations;
                    foreach (Stipulation stipulation in ourStipulations)
                    {
                        if (stipulation.HasEffect(effect))
                        {
                            isActiveForUs = true;
                            result = true;
                            break;
                        }
                    }
                    foreach (Stipulation stipulation in theirStipulations)
                    {
                        if (stipulation.HasEffect(effect))
                        {
                            isActiveForThem = true;
                            result = true;
                        }
                    }
                }
            }
            return result;
        }

        protected internal bool IsStipulationActive(Stipulation stipulationType)
        {
            if (stipulationType == null)
                throw new ArgumentNullException("stipulationType");
            return IsStipulationActive(stipulationType.GetType());
        }

        protected internal bool IsStipulationActive(Type stipulationType)
        {
            if (stipulationType == null)
                throw new ArgumentNullException("stipulationType");
            foreach (Agreement agreement in _activeAgreements)
            {
                foreach (Stipulation stipulation in agreement.GetAllStipulations())
                {
                    if ((stipulation.IsPending || stipulation.IsInEffect)
                        && stipulationType.IsAssignableFrom(stipulation.GetType()))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        protected internal void OnTurnElapsed()
        {
            List<Agreement> inactiveAgreements = new List<Agreement>();

            if ((_inbox != null) && (_inbox.MessageType == DiplomaticMessageType.WarDeclaration))
            {
                OtherRelationship.DeclareWar();
            }

            FindInactiveAgreements(inactiveAgreements);

            _inboxHistory.RemoveWhere(o => ((GameContext.Current.TurnNumber - o.TurnSent) > InboxHistoryThreshold));
            _agreementHistory.RemoveWhere(o => ((GameContext.Current.TurnNumber - o.TurnEnded) > AgreementHistoryThreshold));

            HandleIncomingMessage();

            inactiveAgreements.AddRange(ProcessAgreements());

            Algorithms.SortInPlace(inactiveAgreements, CompareAgreementsByTurnAccepted);

            while (inactiveAgreements.Count > 0)
            {
                RemoveAgreement(inactiveAgreements[0]);
                inactiveAgreements.RemoveAt(0);
            }

            UpdateAttitude();
            UpdateStatus();
        }

        private void UpdateAttitude()
        {
            _attitude.Reset();
            foreach (RelationshipMemory memory in _memories)
            {
                _attitude.AdjustCurrent(memory.Value);
            }
            switch (GameContext.Current.Options.AIMode)
            {
                case AIMode.Aggressive:
                    _attitude.AdjustCurrent(-20);
                    break;
                case AIMode.Agreeable:
                    _attitude.AdjustCurrent(20);
                    break;
            }
        }

        protected internal void UpdateStatus()
        {
            //Unknown = 0,
            //Neutral,
            //AtWar,
            //Peace,
            //Friendly,
            //Affiliated,
            //OwnerIsMember,
            //OtherIsMember,
            //OwnerIsProtectorate,
            //OtherIsProtectorate,
            //OwnerIsSubjugated,
            //OtherIsSubjugated,
            //Allied,
            //Self
            RelationshipStatus currentStatus = RelationshipStatus.Neutral;

            if (!IsContactMade)
            {
                currentStatus = RelationshipStatus.Unknown;
            }
            else if (AtWar && (_activeAgreements.Count == 0))
            {
                currentStatus = RelationshipStatus.AtWar;
            }
            else
            {
                if (IsStipulationActive(typeof(MembershipStipulation)))
                {
                    currentStatus = OwnerCiv.IsEmpire ? RelationshipStatus.OtherIsMember : RelationshipStatus.OwnerIsMember;
                }
                else if (IsStipulationActive(typeof(AllianceStipulation)))
                {
                    currentStatus = RelationshipStatus.Allied;
                }
                else if (IsEffectActive(StipulationEffect.GrantRepairRights)
                    || IsEffectActive(StipulationEffect.ShareIntel)
                    || IsEffectActive(StipulationEffect.ShareSupplies))
                {
                    currentStatus = RelationshipStatus.Affiliated;
                }
                else if (IsEffectActive(StipulationEffect.AllowTrade)
                    || IsEffectActive(StipulationEffect.GrantShipPassage))
                {
                    currentStatus = RelationshipStatus.Friendly;
                }
                else if (IsEffectActive(StipulationEffect.CloseBorders))
                {
                    currentStatus = RelationshipStatus.Peace;
                }
            }

            this.Status = currentStatus;
        }
        #endregion

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        public override int GetHashCode()
        {
            return ((_ownerCivId << 16) | _otherCivId);
        }
    }

    public enum RelationshipStatus : byte
    {
        Unknown = 0,
        Neutral,
        AtWar,
        Peace,
        Friendly,
        Affiliated,
        OwnerIsMember,
        OtherIsMember,
        OwnerIsProtectorate,
        OtherIsProtectorate,
        OwnerIsSubjugated,
        OtherIsSubjugated,
        Allied,
        Self
    }
}